from functools import cached_property, reduce
import re
from .seq import MTSequence, Sequence
from .gene import Transcript, RegionType
from .snv import SNV, TranscriptAnnotation


class DEL(SNV):
    @cached_property
    def s_start(self) -> int:
        return self.pos + 1

    @cached_property
    def s_ref(self) -> Sequence | MTSequence:
        return self.ref[1:]

    @cached_property
    def s_alt(self) -> Sequence | MTSequence:
        """standard alt"""
        return self.alt.__class__("")

    @cached_property
    def size(self) -> int:
        return self.ref.size - 1

    def set_aa_change(self, trans_anno: TranscriptAnnotation, transcript: Transcript, pos: int) -> TranscriptAnnotation:
        cdna = transcript.coding_dna
        new_dna = cdna.substitute(pos, self.s_ref, self.s_alt)
        if transcript.strand == "-":
            cdna = cdna.reverse_complement()
            new_dna = new_dna.reverse_complement()
        start = cdna.difference_simple(new_dna)
        alt = cdna[start - 1 : start + self.s_ref.size - 1]
        if not trans_anno.na_change:
            trans_anno.na_change = f"c.{start}del{alt}" if self.size == 1 else f"c.{start}_{start+self.size-1}del{alt}"
        protein = cdna.protein()
        new_protein = new_dna.protein()
        start, end1, end2 = protein.difference(new_protein)
        aa1 = protein[start - 1 : end1]
        aa2 = new_protein[start - 1 : end2]
        if (cdna.size - new_dna.size) % 3 == 0:
            trans_anno.event = "del_inframe"
            if aa2.size == 0:
                trans_anno.aa_change = f"p.{aa1}{start}del" if aa1.size == 1 else f"p.{aa1[0]}{start}_{aa1[-1]}{end1}del"
                if aa1[-1] == "*":
                    trans_anno.aa_change += "ext*?"
                    trans_anno.event += "_stoploss"
            else:
                trans_anno.aa_change = f"p.{aa1}{start}delins{aa2}" if aa1.size == 1 else f"p.{aa1[0]}{start}_{aa1[-1]}{end1}delins{aa2}"
        else:
            if start < protein.size:
                trans_anno.event = "del_frameshift"
                if aa2.size == 0:
                    trans_anno.aa_change = f"p.{aa1[0]}{start}fs"
                else:
                    if aa2[0] == "*":
                        trans_anno.aa_change = f"p.{aa1[0]}{start}*"
                    else:
                        fsi = new_protein[start - 1 :].find("*")
                        fs = "?" if fsi == -1 else ("" if fsi == 0 else f"{fsi+1}")
                        trans_anno.aa_change = f"p.{aa1[0]}{start}{aa2[0]}fs*{fs}"
        if protein == "M" and new_protein and new_protein[0] != "M":
            trans_anno.event += "_startloss"
        if trans_anno.region.find("splic") != -1:
            trans_anno.event += "_splicing"
        if protein[-1] == "*" and new_protein.find("*") == -1:
            trans_anno.event += "_stoploss"

    def annotate_mrna(self, transcript: Transcript) -> TranscriptAnnotation:
        region1, cds_size1, utr_size1 = transcript.find_region(self.s_start)
        region2, cds_size2, utr_size2 = transcript.find_region(self.s_end)
        trans_anno = TranscriptAnnotation.from_transcript(transcript, region1, region2)

        if region1 != region2:
            if region2.start - region1.end > 1:
                trans_anno.region = "deletion"
            else:
                if {region1.type, region2.type} == {RegionType.CDS, RegionType.INTRON}:
                    trans_anno.region = "exonic_splicing"
        if (self.s_start < transcript.cds_start or self.s_start >= transcript.cds_end) and (self.s_end < transcript.cds_start or self.s_end >= transcript.cds_end):
            # snv包含了整个编码区，即整个编码区被删除 SNV的起始终止位置都不在CDS区或CDS区之间的Intron中
            # 情况2: snv包含了整个编码区，即整个编码区被删除
            #   ...+++,,,+++...
            #     |---------|
            #  |---------------|
            # 情况2: snv发生在整个编码区左边
            #   |-|...+++,,,+++...
            #     |-|
            # 情况3: snv发生在整个编码区左边
            #  ...+++,,,+++...|-|
            #               |-|
            distance1 = transcript.calc_utr_distance(region1, utr_size1, self.s_start)
            distance2 = transcript.calc_utr_distance(region2, utr_size2, self.s_end)
            if distance1 == distance2:
                trans_anno.na_change = f"c.{distance1}del"
            else:
                trans_anno.na_change = f"c.{distance1}_{distance2}del" if transcript.strand == "+" else f"c.{distance2}_{distance1}del"
            if self.s_start < transcript.cds_start and self.s_end >= transcript.cds_end:
                # 情况1
                trans_anno.region = "transcript"
                trans_anno.region2 = "transcript"
                trans_anno.event = "deletion"
        elif self.s_start < transcript.cds_start <= self.s_end <= transcript.cds_end:
            #  ...+++,,,+++...
            #   |--|
            # |----|
            distance1 = transcript.calc_utr_distance(region1, utr_size1, self.s_start)
            distance2 = transcript.calc_cds_distance(region2, cds_size2, self.s_end)
            trans_anno.na_change = f"c.{distance1}_{distance2}del" if transcript.strand == "+" else f"c.{distance2}_{distance1}del"
        elif transcript.cds_start <= self.s_start <= transcript.cds_end < self.s_end:
            # ...,,,+++...
            #        |--|
            #        |----|
            distance1 = transcript.calc_cds_distance(region1, cds_size1, self.s_start)
            distance2 = transcript.calc_utr_distance(region2, utr_size2, self.s_end)
            trans_anno.na_change = f"c.{distance1}_{distance2}del" if transcript.strand == "+" else f"c.{distance2}_{distance1}del"
        else:
            # 变异跨region
            distance1 = transcript.calc_cds_distance(region1, cds_size1, self.s_start)
            distance2 = transcript.calc_cds_distance(region2, cds_size2, self.s_end)
            if region1 != region2:
                if distance1 == distance2:
                    trans_anno.na_change = f"c.{distance1}del"
                else:
                    trans_anno.na_change = f"c.{distance1}_{distance2}del" if transcript.strand == "+" else f"c.{distance2}_{distance1}del"
            else:
                if region1.type == RegionType.INTRON:
                    if distance1 == distance2:
                        trans_anno.na_change = f"c.{distance1}del"
                    else:
                        trans_anno.na_change = f"c.{distance1}_{distance2}del" if transcript.strand == "+" else f"c.{distance2}_{distance1}del"
                    values = list(
                        map(
                            int,
                            reduce(
                                lambda x, y: x + y,
                                map(
                                    lambda z: re.findall(r"[\+\-](\d+)$", z),
                                    [distance1, distance2],
                                ),
                            ),
                        )
                    )
                    if values and min(values) <= 2:
                        trans_anno.event = "splicing"
                        trans_anno.region = "splicing"
                else:
                    pos = cds_size1 + (self.s_start - region1.start + 1)
                    self.set_aa_change(trans_anno, transcript, pos)
        return trans_anno

    def annotate_ncrna(self, transcript: Transcript) -> TranscriptAnnotation:
        region1, _, _ = transcript.find_region(self.s_start)
        region2, _, _ = transcript.find_region(self.s_end)
        trans_anno = TranscriptAnnotation.from_transcript(transcript, region1, region2)
        trans_anno.region = "ncRNA"
        if self.s_start < transcript.tx_start:
            if self.s_end > transcript.tx_end:
                distance1, distance2 = (
                    transcript.tx_start - self.s_start,
                    self.s_end - transcript.tx_end,
                )
                trans_anno.na_change = f"n.-{distance1}_+{distance2}del" if transcript.strand == "+" else f"n.-{distance2}_-{distance1}del"
            else:
                trans_anno.na_change = (
                    f"n.-{transcript.tx_start-self.s_start}_{self.s_end-transcript.tx_start+1}del"
                    if transcript.strand == "+"
                    else f"n.{transcript.tx_end-self.s_end+1}_+{transcript.tx_start-self.s_start}del"
                )
        else:
            if self.s_end > transcript.tx_end:
                trans_anno.na_change = (
                    f"n.{transcript.tx_start-self.s_start+1}_{self.s_end-transcript.tx_end}del"
                    if transcript.strand == "+"
                    else f"n.-{self.s_end-transcript.tx_end}_{transcript.tx_end-self.s_start+1}del"
                )
            else:
                start = self.s_start - transcript.tx_start + 1
                end = self.s_end - transcript.tx_start + 1
                dna = transcript.dna
                new_dna = dna.delete(start, end)
                if transcript.strand == "-":
                    dna = dna.reverse_complement()
                    new_dna = new_dna.reverse_complement()
                pos = dna.difference_simple(new_dna)
                trans_anno.na_change = f"n.{pos}del" if start == end else f"n.{pos}_{pos+end-start}del"
        return trans_anno
